#pragma once
#pragma warning(disable : 4996)
#include <Windows.h>
#include <vector>
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>

#include <codecvt>
#include <locale>
#include <stdexcept>

#include <Psapi.h>
#include <d3d11.h>
#include <TlHelp32.h>
#include <AutoFree.h>

#define DBG_PRINT(x, ...) printf_s("%s[LINE %d]:%s\n", __FILE__, __LINE__, FormatA(x, __VA_ARGS__))
#define DBG_PRINT_LINE DBG_PRINT("EXEC")
#ifndef RELOC
#define RELOC(p, o) (void *)((char *)p ? (((char *)p + o + 4) + (*(int *)((char *)p + o))) : NULL)
#endif
#ifndef typeof
#define typeof(x) decltype(x)
#endif // !typeof

#ifndef PROPERTY
#define PROPERTY(t, n) __declspec(property(put = property__set_##n, get = property__get_##n)) t n
#define READONLY_PROPERTY(t, n) __declspec(property(get = property__get_##n)) t n
#define WRITEONLY_PROPERTY(t, n) __declspec(property(put = property__set_##n)) t n
#define GET(t, n) t property__get_##n()
#define SET(t, n) void property__set_##n(t value)
#endif // !PROPERTY

static std::string WebRequest(const char *url)
{
#define BLOCK_READ 4096
	IStream *stream;
	HRESULT result = URLOpenBlockingStreamA(0, url, &stream, 0, 0);
	if (result != 0)
	{
		return "";
	}
	char buffer[BLOCK_READ];
	unsigned long bytesRead;
	std::stringstream ss;
	stream->Read(buffer, BLOCK_READ, &bytesRead);
	while (bytesRead > 0U)
	{
		ss.write(buffer, (long long)bytesRead);
		stream->Read(buffer, BLOCK_READ, &bytesRead);
	}
	stream->Release();
	return ss.str();
}
static BYTE *_get_jmp_address(BYTE *ptr)
{
	if (*ptr == 0xE9)
	{
		return (ptr + 5) + (*(UINT32 *)&ptr[1]);
	}
	return ptr;
}
static int _get_index(BYTE *ptr)
{
	if (*(UINT16 *)ptr == 0x20FF)
	{
		return 0;
	}
	if (*(UINT16 *)ptr == 0x60FF)
	{
		return ptr[2] / sizeof(void *);
	}
	if (*(UINT16 *)ptr == 0xA0FF)
	{
		return *(UINT32 *)&ptr[2] / sizeof(void *);
	}
	return -1;
}
template <typename T>
static int GetVTableIndex(T funcaddr)
{
	void *proc = (void *&)funcaddr;
	BYTE *addr = _get_jmp_address((BYTE *)proc);
	for (int i = 0; i < 32; i++)
	{
		if (addr[i] == 0xFF)
		{
			auto dx = _get_index(&addr[i]);
			if (dx >= 0)
				return dx;
		}
	}
	return -1;
}
static VOID read_memory_bytes(UINT64 ptr, SIZE_T size, PVOID buff)
{
	if (ptr < 0x1000000 - 0x8 || ptr > 0x7FFFFFF00000 + 0x8)
	{
		__stosb((PBYTE)buff, 0, size);
		return;
	}
	else
	{
		if (IsBadReadPtr((PVOID)ptr, size))
		{
			__stosb((PBYTE)buff, 0, size);
			return;
		}
		else
		{
			__movsb((PBYTE)buff, (BYTE *)ptr, size);
			return;
		}
	}
	return;
}
static VOID write_memory_bytes(UINT64 ptr, SIZE_T size, PVOID buff)
{
	if (ptr < 0x1000000 - 0x8 || ptr > 0x7FFFFFF00000 + 0x8)
	{
		__stosb((PBYTE)buff, 0, size);
		return;
	}
	else
	{
		if (!IsBadReadPtr((PVOID)ptr, size))
		{
			DWORD lpflOldProtect = 0;
			VirtualProtect((PVOID)ptr, size, PAGE_EXECUTE_WRITECOPY, &lpflOldProtect);
			__movsb((BYTE *)ptr, (PBYTE)buff, size);
			VirtualProtect((PVOID)ptr, size, lpflOldProtect, &lpflOldProtect);
			return;
		}
	}
	return;
}
static __int64 SearchBytesA(unsigned char *a, __int64 lena, unsigned char *b, __int64 lenb, __int64 startp)
{
	if (startp == -1)
	{
		startp = 0;
	}
	for (__int64 i = startp; i < lena; i++)
	{
		for (__int64 x = 0; x < lenb; x++)
		{
			if (b[x] == 0xCC)
			{
				continue;
			}
			if (a[i + x] != b[x])
			{
				break;
			}

			if (x == lenb - 1)
			{
				return i;
			}
		}
	}
	return -1;
}
static __int64 FindAsmCode(unsigned char *code, int code_size, __int64 startbase, __int64 basesize)
{
	unsigned char *p = new unsigned char[0x1000];

	for (__int64 i = 0; i < basesize; i += 0x1000)
	{

		read_memory_bytes(startbase + i, 0x1000, p);

		__int64 index = SearchBytesA(p, 0x1000, code, code_size, 0);

		if (index != -1)
		{
			delete[] p;
			return i + index;
		}
	}
	delete[] p;
	return -1;
}
static bool InRect(POINT point, POINT LOC, SIZE SIZ)
{
	int xf = point.x - LOC.x;
	int yf = point.y - LOC.y;
	return xf >= 0 && xf <= SIZ.cx &&
		   yf >= 0 && yf <= SIZ.cy;
}
static DWORD NtOSBuildVersion()
{
	static DWORD res = 0;
	if (!res)
	{
		typedef LONG(NTAPI * fnRtlGetVersion)(PRTL_OSVERSIONINFOW lpVersionInformation);
		fnRtlGetVersion pRtlGetVersion = NULL;
		while (pRtlGetVersion == 0)
		{
			HMODULE ntdll = GetModuleHandleA("ntdll.dll");
			if (ntdll)
			{
				pRtlGetVersion = (fnRtlGetVersion)GetProcAddress(ntdll, "RtlGetVersion");
			}
		}
		RTL_OSVERSIONINFOW osversion{};
		pRtlGetVersion(&osversion);
		res = osversion.dwBuildNumber;
	}
	return res;
}
static wchar_t *CurrentProcessName()
{
	TCHAR Buffer[MAX_PATH];
	GetModuleFileName(NULL, Buffer, MAX_PATH);
	return Buffer;
}
#define MAX_WBUFFER_LEN 0x8000
template <typename... Types>
static wchar_t *FormatW(const wchar_t *fmt, Types... args)
{
	static wchar_t TMP_[0x1000];
	wmemset(TMP_, 0, 0x1000);
	swprintf_s(TMP_, fmt, args...);
	return TMP_;
}
static wchar_t *ITOW(int i)
{
	return FormatW(L"%d", i);
}
static wchar_t *FTOW(float i)
{
	return FormatW(L"%f", i);
}

template <typename... Types>
static char *FormatA(const char *fmt, Types... args)
{
	static char TMP_[MAX_WBUFFER_LEN];
	TMP_[sprintf_s(TMP_, MAX_WBUFFER_LEN, fmt, args...)] = '\0';
	return TMP_;
}

static HWND GetWindowInProcess(const char *title, ULONG pid)
{
	static char title_tmp[128];
	static ULONG pid_tmp = 0;
	static HWND result = NULL;
	pid_tmp = pid;
	strcpy_s(title_tmp, title);
	EnumWindows([](HWND hwnd, LPARAM lParam) -> BOOL
				{
			char caption[200];
	memset(caption, 0, sizeof(caption));
	GetWindowTextA(hwnd, caption, 200);
	ULONG thispid = 0;
	GetWindowThreadProcessId(hwnd, &thispid);
	if (strcmp(caption, title_tmp) == 0 && thispid == pid_tmp)
	{
		result = hwnd;
	}
	return TRUE; },
				NULL);
	return result;
}
static DWORD GetPidByProcessName(const wchar_t *ProcName)
{
	DWORD Pid = 0;
	HANDLE Snapshot = CreateToolhelp32Snapshot(2, 0);
	PROCESSENTRY32W Entry;
	ZeroMemory(&Entry, sizeof(Entry));
	Entry.dwSize = sizeof(Entry);
	if (Process32FirstW(Snapshot, &Entry))
	{
		do
		{
			if (_wcsicmp(Entry.szExeFile, ProcName) == 0)
			{
				Pid = Entry.th32ProcessID;
				break;
			}
		} while (Process32NextW(Snapshot, &Entry));
	}

	return Pid;
}
static void PrintHex(void *ptr, int count, int sizeperline)
{
	const char keys[] = "0123456789ABCDEF";
	BYTE *tmp = (BYTE *)ptr;
	for (BYTE *b = tmp; b < tmp + count; b += sizeperline)
	{
		for (int j = 0; j < sizeperline && b + j < tmp + count; j++)
		{
			printf("%c%c ", keys[b[j] / 0x10], keys[b[j] % 0x10]);
		}
		printf("\n");
	}
}
static void PrintHex(void *ptr, int count)
{
	const char keys[] = "0123456789ABCDEF";
	BYTE *tmp = (BYTE *)ptr;
	for (BYTE *b = tmp; b < tmp + count; b++)
	{
		printf("%c%c ", keys[*b / 0x10], keys[*b % 0x10]);
	}
}